iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 16
1
Modern Web

WebGIS入門學習 - 以Openlayers實作系列 第 16

Day 16. 全台河川水位即時資料介接

  • 分享至 

  • xImage
  •  

前言

今天來進行河川水位站的介接,回歸寫程式囉~

水位站資料API介接

水位站資料:

水位警戒值資料:

河川水位的資料可由上述兩者API進行介接,民生公共物聯網的API遵循 OGC SensorThings API 標準,架構較為複雜一點,使用者需要花一些時間了解,而WRA API則是屬於 oData 服務的api,可以利用$filter來進行特定資料的過濾(ex.只撈出花蓮縣的水位站資料)。

由於本功能希望藉由水位即時資料與水位警戒資料進行判斷是否達到警戒值,若達到不同等級的警戒值,其燈號顯示應與水利署的規範一致,並同時展示在地圖上方便一眼看出哪個地方到達水位警戒,因此除了即時測站水位外,也需要警戒值的資料。

由於WRA API已有提供警戒值的資訊可以介接,且形式較為簡單 (不然又要花整篇的時間解釋OGC SensorThings API 標準了),本次就直接接這支api進行功能的開發囉~
若大家有興趣可以改民生公共物聯網的API試試看


建立ExpertModule/WaterLevel.html頁面,預計要顯示所有水位站的列表,包含所在地、測站名、即時水位高(公尺)、警戒燈號等資訊。
預計規劃要顯示撈取資料的時間重新整理 (執行waterlevel.getWeterLevelData()) 按鈕。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
    <style>
        div.circle {
            width: 20px;
            height: 20px;
            border-radius:999em;
            display: inline-block;
            cursor: pointer;
        }
            div.circle.normal {
                background-color: #43ef0a;
            }

            div.circle.WarningLevel3 {
                background-color: yellow;
            }

            div.circle.WarningLevel2 {
                background-color: orange;
            }

            div.circle.WarningLevel1 {
                background-color: red;
            }

            div.circle.unknown {
                background-color: gray;
            }
    </style>
</head>
<body>
    <h2>河川水位查詢</h2>
    <!-- 重新整理按鈕 -->
    <div style="float:right; padding:0px 10px 5px 10px; cursor:pointer;" id="waterlevelLoadingInfo" onclick="waterlevel.getWeterLevelData()"><i class="sync alternate blue icon"></i></div>
    <!-- 資料時間 -->
    <div style="float:right; padding:0px 0px 5px 0px; color:#2486d0;" id="waterlevelTime"></div>
    <div>
        <table class="ui compact center aligned table">
            <thead>
                <tr>
                    <th style="width:65px;">所在地</th>
                    <th>測站名</th>
                    <th>水位高(公尺)</th>
                    <th style="width:50px;">警戒燈號</th>
                </tr>
            </thead>
            <tbody id="waterlaveltable">
            </tbody>
        </table>
    </div>
    <script type="text/javascript" src="map_module/widget/ExpertModule/jWaterLevel.js"></script>
    <script>
        waterlevel.getWeterLevelData();
    </script>
</body>
</html>

建立ExpertModule/jWaterLevel.js頁面

var waterlevel = function () {
    var stationObj;
    var RealTimeInfoObj;

    return {
        getWeterLevelData:getWeterLevelData,
        checkLayerValid:checkLayerValid,
        showData:showData,
        locateWaterStation:locateWaterStation
    };
}();

首先依慣例,先建一個專屬於水位站資料的layer,id為waterlevelLyr,應每次在撈取資料時檢查這個Layer是否存在checkLayerValid()

  • 是 => 清除內容
  • 否 => 創建layer並加到地圖當中
function checkLayerValid() {
    if (map.e_getLayer("weterlevelLyr") === undefined) {
        weterlevelLyr = new ol.layer.Vector({
            source: new ol.source.Vector({})
        });
        weterlevelLyr.id = "weterlevelLyr";
        map.addLayer(weterlevelLyr);
    } else {
        weterlevelLyr = map.e_getLayer("weterlevelLyr");
        weterlevelLyr.getSource().clear();
    }
}

接入水位站資料,但由於水位即時資料只有站點的ID StationNo,而沒有測站的資料,水位基本資料(警戒值)的api有提供測站基本資料,因此在最後組出頁面之前必須這兩個成果都要得到。

  • 水位即時資料
    • 輸出
      • WaterRealTimeInfo {
        StationNo (string): 水位站站碼 ,
        Time (string): 水情時間(格式:yyyy-MM-dd HH:mm) ,
        WaterLevel (number): 水位高(公尺)
        }
  • 水位基本資料
    • 輸出
      • WaterStation {
        Address (string, optional): 水位站所在地址 ,
        CityCode (string): 縣市代碼 ,
        WarningLevel1 (number, optional): 一級警戒值(公尺) ,
        WarningLevel2 (number, optional): 二級警戒值(公尺) ,
        WarningLevel3 (number, optional): 三級警戒值(公尺) ,
        TopLevel (number, optional): 水位堤頂高(公尺) ,
        Latitude (number, optional): 緯度(WGS84) ,
        Longitude (number, optional): 經度(WGS84) ,
        PlanFloodLevel (number, optional): 計畫洪水位(公尺) ,
        StationNo (string): 測站代碼 ,
        StationName (string): 測站中文名稱 ,
        BasinNo (string): 流域代碼 ,
        BasinName (string): 流域名稱
        }

這部分可以寫call back去執行,也可以直接寫兩個ajax串起來,只是這樣就會有前後載入的等待時間。
StationNo為key,將兩組資料串起來,但兩組資料的筆數不會相同,所以有以下判斷邏輯:

  • 即時資料沒有該測站、警戒值資料有 => 不顯示該站
  • 即時資料有該測站、警戒值資料沒有 => 以灰色顯示 (因不知道有沒有超過警戒值)

判斷完後直接將基本資料和警戒值資料併入即時資料內,最後呼叫showData()

function getWeterLevelData() {
    $("#waterlevelLoadingInfo i").addClass("loading");
    $("#waterlaveltable").html("");
    $("#waterlevelTime").html("");
    waterlevel.checkLayerValid();
    // 水位站警戒值
    $.ajax({
        type: "GET",
        url: "https://fhy.wra.gov.tw/WraApi/v1/Water/Station?$orderby=Address",
        dataType: "json",
        contentType: "application/json; charset=utf-8",
        success: function (d) {
            stationObj = d;
            // 即時水位資料
            $.ajax({
                type: "GET",
                url: "https://fhy.wra.gov.tw/WraApi/v1/Water/RealTimeInfo?$orderby=StationNo",
                dataType: "json",
                contentType: "application/json; charset=utf-8",
                success: function (d) {
                    RealTimeInfoObj = d;
                    RealTimeInfoObj.forEach(function (item, index) {
                        var spobj = stationObj.filter(k => k.StationNo === item.StationNo);
                        if (spobj.length > 0) {
                            var keyarr = Object.keys(spobj[0]);
                            for (let i = 0; i < keyarr.length; i++) {
                                RealTimeInfoObj[index][keyarr[i]] = spobj[0][keyarr[i]];
                            }
                        }
                    });
                    waterlevel.showData(RealTimeInfoObj, 'waterlaveltable');
                },
                error: function (jqXHR, exception) {
                    ajaxError(jqXHR, exception);
                }
            });
        },
        error: function (jqXHR, exception) {
            ajaxError(jqXHR, exception);
        }
    });
}

撰寫showData()進行資料的展示,依據水利署規範進行燈號的顯示,規範如下:

  • 未達警戒水位:地圖以綠色表示。
  • 三級警戒水位:河川水位預計未來2小時到達高灘地之水位。(地圖以黃色表示三級)。
  • 二級警戒水位:河川水位預計未來5小時到達計畫洪水位(或堤頂)時之水位。(地圖以橘色表示二級)。
  • 一級警戒水位:河川水位預計未來2小時到達計畫洪水位(或堤頂)時之水位。(地圖以紅色表示一級)。

https://ithelp.ithome.com.tw/upload/images/20200924/20108631Ad2pz2Wro9.png
參考來源:水利署NCDR

因撈取的資料的坐標系統為4326,可參考 Day 13 進行坐標系統的轉換,將坐標轉至圖台底圖使用之3857坐標,並於地圖上展示水位站位置。

規劃當顯示燈號時,該燈號圖示有三種功用:

  1. 顯示警示狀態 (組 weterlevelhtml)。
  2. 滑鼠游標指上去顯示三個警示資料 (組 lighttitle)。
  3. 當點選它則可以定位到該點測站 (執行 locateWaterStation())。
function showData(obj, domid) {
    $("#" + domid).html("");
    waterlevel.checkLayerValid();
    var levelalertlight = '';
    for (let i = 0; i < obj.length; i++) {
        if (obj[i].Latitude !== undefined) {
            var pointfeature = helper.transOlGeometry_4326to3857(new ol.Feature({
                        geometry: new ol.geom.Point([obj[i].Longitude, obj[i].Latitude])
                    }));
            pointfeature.info = '測站:' + obj[i].StationName + "</br>流域名稱:" + obj[i].BasinName + "</br>目前水位高度:" + obj[i].WaterLevel + " m";
            var fill;
            if (obj[i].WaterLevel !== undefined && !(obj[i].WarningLevel1 === undefined && obj[i].WarningLevel2 === undefined && obj[i].WarningLevel3 === undefined)) {

                if (obj[i].WarningLevel1 !== undefined && obj[i].WaterLevel > obj[i].WarningLevel1) {
                    levelalertlight = '<div class="circle WarningLevel1"></div>';
                    fill = new ol.style.Fill({
                        color: 'rgba(255,0,0,0.7)'
                    });
                } else if (obj[i].WarningLevel2 !== undefined && obj[i].WaterLevel > obj[i].WarningLevel2) {
                    levelalertlight = '<div class="circle WarningLevel2"></div>';
                    fill = new ol.style.Fill({
                        color: 'rgba(255,165,0,0.7)'
                    });
                } else if (obj[i].WarningLevel3 !== undefined && obj[i].WaterLevel > obj[i].WarningLevel3) {
                    levelalertlight = '<div class="circle WarningLevel3"></div>';
                    fill = new ol.style.Fill({
                        color: 'rgba(255,255,0,0.7)'
                    });
                } else {
                    levelalertlight = '<div class="circle normal"></div>';
                    fill = new ol.style.Fill({
                        color: 'rgba(67,239,10,0.7)'
                    });
                }
            } else {
                levelalertlight = '<div class="circle unknown"></div>';
                fill = new ol.style.Fill({
                    color: 'rgba(128,128,128,0.7)'
                });
            }
            var stroke = new ol.style.Stroke({
                color: '#767676',
                width: 1
            });
            pointfeature.setStyle(new ol.style.Style({
                    image: new ol.style.Circle({
                        fill: fill,
                        stroke: stroke,
                        radius: 8
                    }),
                    fill: fill,
                    stroke: stroke
                }));
            weterlevelLyr.getSource().addFeature(pointfeature);
            var x3857 = pointfeature.getGeometry().getCoordinates()[0];
            var y3857 = pointfeature.getGeometry().getCoordinates()[1];
            // 燈號mouseover顯示警戒值備註
            var lighttitle = '第一警戒值:' + (obj[i].WarningLevel1 === undefined ? '-- ' : obj[i].WarningLevel1) + 'm\n第二警戒值:' + (obj[i].WarningLevel2 === undefined ? '-- ' : obj[i].WarningLevel2) + 'm\n第三警戒值:' + (obj[i].WarningLevel3 === undefined ? '-- ' : obj[i].WarningLevel3) + 'm';
            // 水位測站列表
            var weterlevelhtml = '<tr><td class="middle aligned">' + (obj[i].Address === undefined ? '-' : obj[i].Address.substring(0, 6)) + '</td><td class="middle aligned">' + (obj[i].StationName === undefined ? '-' : obj[i].StationName) + '</td><td class="middle aligned">' + (obj[i].WaterLevel === undefined ? '-' : obj[i].WaterLevel) + '</td><td class="middle aligned" title="' + lighttitle + '" onclick="waterlevel.locateWaterStation(\'' + x3857 + '\',\'' + y3857 + '\')">' + levelalertlight + '</td></tr>';
            $("#" + domid).append(weterlevelhtml);
        }
    }
    $("#waterlevelTime").html(obj[0].Time);
    $("#waterlevelLoadingInfo i").removeClass("loading");
}

點選燈號執行 locateWaterStation(),定位到該點。

function locateWaterStation(x, y) {
    map.e_centerAndZoom(new ol.Feature({
            geometry: new ol.geom.Point([x, y])
        }), 5);
}

https://ithelp.ithome.com.tw/upload/images/20200926/20108631is7jZa7KyY.png


小結

今天完成了即時水位站的介接、測站基本資料與警戒值的介接,撰寫更新資料的button等功能。
此功能主要是要了解如何介接、串資料、展示資料、建立資料更新機制,完整走過一整個介接流程,但圖面上只有顯示燈號無法得知該點是哪個測站,因此明天就要來寫popup info功能,點選圖面上的圖徵,即可顯示該點相關資訊。


上一篇
Day 15. 今天不寫程式改來學知識 #3:Local坐標系之正形與仿射轉換
下一篇
Day 17. WebGIS 的 Popup 點選圖面屬性顯示
系列文
WebGIS入門學習 - 以Openlayers實作30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言